package nachos.userprog;

import nachos.machine.*;
import nachos.threads.*;
import nachos.userprog.*;
import nachos.userprog.UserKernel.Node;

import java.util.LinkedList;
import java.util.Iterator;
import java.util.TreeMap;

/**
 * A kernel that can support multiple user processes.
 */
public class UserKernel extends ThreadedKernel {
	/**
	 * Allocate a new user kernel.
	 */
	public UserKernel() {
	super();
//Part 3
//processPool = new TreeMap<Integer, UserProcess>();
	list = new myList();
//End Part 3
	}

	/**
	 * Initialize this kernel. Creates a synchronized console and sets the
	 * processor's exception handler.
	 */
	public void initialize(String[] args) {
		super.initialize(args);
	
		console = new SynchConsole(Machine.console());
		
		Machine.processor().setExceptionHandler(new Runnable() {
			public void run() { exceptionHandler(); }
			});
		
		/**************************************************
		 *   Part II - Adding Multiprogramming Support
		 *   Initializing Physical Memory (info on pages)
		 **************************************************/
		lock = new Lock();
		
		numberOfPages = Machine.processor().getNumPhysPages();
		for (int i = 0; i < numberOfPages; i++) {
			PageEntry pageEntry = new PageEntry();
			pageEntry.setAvailable(true);
			pageEntry.setIndex(i);
			freePagesLinkedList.add(pageEntry);
		}
		/**************************************************/
	}
	
	/**************************************************
	 *   Part II - Adding Multiprogramming Support
	 *   getPage()
	 *   freePage()
	 *   isFull()
	 **************************************************/
	// Check freePagesLinkedList if full (false if find one PageEntry available)
	public boolean isFull() {
		for (int i = 0; i < freePagesLinkedList.size(); i++) {
			if (((PageEntry)freePagesLinkedList.get(i)).isAvailable()) {
				return false;
			}
		}
		//Lib.debug('a', "Out of Physical Memory");
		return true;
	}
	
	// Get an available page from freePageLinkedList
	public int getPage() {
		lock.acquire();
		int pageNumber = 0;
		
		if (this.isFull()) {
			lock.release();
			return -1;
		}
		
		for(int i = 0; i < numberOfPages; i++) {
			int indexChecker;
			  if(((PageEntry)freePagesLinkedList.get(i)).isAvailable())
		        indexChecker = i;
		        else
		        	indexChecker = -1;
			if (indexChecker >= 0) {
				PageEntry pageEntry = (PageEntry)freePagesLinkedList.get(i);
				pageNumber = pageEntry.getIndex();
				pageEntry.setAvailable(false);
				
				//Lib.debug('a', "Allocating page: " + i);
				lock.release();
				return pageNumber;
			}
		}
		lock.release();
		return -1;
	}
	
	// Free a page
	public void freePage(int i) {
		lock.acquire();
		if (i >= 0 && i < freePagesLinkedList.size())
			((PageEntry)freePagesLinkedList.get(i)).setAvailable(true);
		else
			Lib.debug('a', "Invalid Page Index");
		lock.release();
	}
	
	/**************************************************/

	/**
	 * Test the console device.
	 */	
	public void selfTest() {
	super.selfTest();

	/*
	System.out.println("Testing the console device. Typed characters");
	System.out.println("will be echoed until q is typed.");
	
	char c;

	do {
		c = (char) console.readByte(true);
		console.writeByte(c);
	}
	while (c != 'q');

	System.out.println("");
	*/
	}

	/**
	 * Returns the current process.
	 *
	 * @return	the current process, or <tt>null</tt> if no process is current.
	 */
	public static UserProcess currentProcess() {
	if (!(KThread.currentThread() instanceof UThread))
		return null;
	
	return ((UThread) KThread.currentThread()).process;
	}

	/**
	 * The exception handler. This handler is called by the processor whenever
	 * a user instruction causes a processor exception.
	 *
	 * <p>
	 * When the exception handler is invoked, interrupts are enabled, and the
	 * processor's cause register contains an integer identifying the cause of
	 * the exception (see the <tt>exceptionZZZ</tt> constants in the
	 * <tt>Processor</tt> class). If the exception involves a bad virtual
	 * address (e.g. page fault, TLB miss, read-only, bus error, or address
	 * error), the processor's BadVAddr register identifies the virtual address
	 * that caused the exception.
	 */
	public void exceptionHandler() {
	Lib.assertTrue(KThread.currentThread() instanceof UThread);

	UserProcess process = ((UThread) KThread.currentThread()).process;
	int cause = Machine.processor().readRegister(Processor.regCause);
	process.handleException(cause);
	}

	/**
	 * Start running user programs, by creating a process and running a shell
	 * program in it. The name of the shell program it must run is returned by
	 * <tt>Machine.getShellProgramName()</tt>.
	 *
	 * @see	nachos.machine.Machine#getShellProgramName
	 */
	public void run() {
	super.run();

	UserProcess process = UserProcess.newUserProcess();
	
	String shellProgram = Machine.getShellProgramName();	
	Lib.assertTrue(process.execute(shellProgram, new String[] { }));

	KThread.currentThread().finish();
	}

	/**
	 * Terminate this kernel. Never returns.
	 */
	public void terminate() {
	super.terminate();
	}

	/** Globally accessible reference to the synchronized console. */
	public static SynchConsole console;

	// dummy variables to make javac smarter
	private static Coff dummy1 = null;
	
	/**************************************************
	 *   Part II - Adding Multiprogramming Support
	 *   Necessary Data Members and Classes
	 **************************************************/
	private Lock lock;
	public int numberOfPages;
	private LinkedList freePagesLinkedList = new LinkedList<Integer>();
	
	private class PageEntry {
		private int index = 0;
		private boolean available = true;
		
		// get and set functions
		private int getIndex() {
			return this.index;
		}
		private boolean isAvailable() {
			return this.available;
		}
		private void setIndex(int _index) {
			this.index = _index;
		}
		private void setAvailable(boolean _available) {
			this.available = _available;
		}
	}
	/**************************************************/

	//Part 3
	private int processID = 0;
	//public TreeMap<Integer,Node> processPool;
	public class myList {
		LinkedList<Node> pool = new LinkedList();
		LinkedList<Integer> poolID = new LinkedList();
	}
	myList list;

	public int getPID() {
		int id = processID;
		processID++;
		return id;
	}

	public int insertTree(int parent, UserProcess blah) {
		int ID = getPID();
		Node nodeD = new Node(blah,parent,ID);
		//processPool.put(ID, nodeD);
		list.pool.add(nodeD);
		list.poolID.add(ID);
		return ID;
	}

	public boolean isThere(int searchID) {
		//boolean okay = processPool.containsKey(searchID);
		//return okay;
		return list.poolID.contains(searchID);
	}


	public Node findNode(int ID) {
		//return processPool.get(ID);
		return list.pool.get(list.poolID.indexOf(ID));
	}



	public void removeParent(int parent) {
		//Iterator it = processPool.keySet().iterator();
		Node fodder;
		int countb = 0;
		while(countb < list.pool.size()) {
			//fodder = processPool.get(it.next());
			fodder = list.pool.get(countb);
			countb++;
			if(fodder.parent == parent)
				fodder.parent = -1;
		}
	}

	public boolean last(int ID) {
		//Iterator it  = processPool.keySet().iterator();
		Iterator<Node> it = list.pool.iterator();
		Node fodder;
		int count = 0;
		int countb = 0;
		while(it.hasNext()){//while(countb < list.pool.size()) {
			//fodder = processPool.get(it.next());
			fodder = it.next();//list.pool.(it.next());
			System.out.println("checking " + countb);
			countb++;
			System.out.println(fodder.process_ID + " " + fodder.running);
			if((fodder.process_ID != ID && fodder.running)){
				System.out.println("IM HERE"+count);
				count++;}
		}

		if(count == 0)
			return true;
		else
			return false;
	}
	
	
	public void setParent(int child, int parent) {
		Node nodeC = findNode(child);
		nodeC.parent = parent;
	}
	
	public int findParent(int child) {
		return findNode(child).parent;
	}
	
	public void errorFailedCall (int child) {
		Node node = findNode(child);
		//System.out.println("I AM AN ERROR" + node.process_ID);
		node.running = false;
		node.retval = -1;
		node = findNode(node.parent);
		node.error = true;
	}
	public boolean checkError(int ID) {
		return (findNode(ID).error == true);
	}
	public boolean checkRunning(int ID) {
		return (findNode(ID).running == true);
	}
	public int getRet(int ID) {
		return (findNode(ID).retval);
	}
	public UserProcess getProc(int ID) {
		return (findNode(ID).process);
	}
	
	public void setFinish(int ID, int ret) {
		removeParent(ID);
		Node nodeB = findNode(ID);
		nodeB.retval = ret;
		System.out.println("AM I going to finish?" + ID);
		nodeB.running = false;
		
	}
	
	
	
	public class Node
	{
		int process_ID;
		int parent;
		int retval;
		boolean running;
		boolean error;
		UserProcess process;
		
		public Node(UserProcess process, int parent, int process_ID)
		{
			this.process_ID = process_ID; 
			this.parent = parent; 
			this.process = process; 
			this.running = true; 
			this.error = false;
		}
		
	}
public static final String m_swapFileName = new String(".swap_ab3eci.tmp");

}

